אחד הדברים החשובים ביותר בבניית אתר גדול, זו מערכת השפות, כדאי להכין כל אתר לאפשרות של הוספת שפה בעתיד, או כבר בבניית האתר.
לפניכם מדריך לבניית מערכת שפות מבוססת XML, מתאים גם לMVC.
השלב הראשון בבניית מערכת לבניית שפות הוא בניית דף להכנסת ערכים לקובץ הXML.
לא הוספתי במערכת אפשרות להוספה עתידית של שפות נוספות, כי אני עובד עם עברית ואנגלית בלבד.
אז או שתגדירו את השפות הנחוצות מראש, או שתוסיפו שורת קוד שתבצע את זה כשתצטרכו.
אז בואו נכיר את הXML שלנו, נקרא לו lang.xml.
<?xml version="1.0" encoding="utf-8"?>
<lang>
<text id="1">
<he>ברוך הבא</he>
<en>Welcome</en>
</text>
<text id="2">
<he>שם משתמש</he>
<en>username</en>
</text>
</lang>
<lang>
<text id="1">
<he>ברוך הבא</he>
<en>Welcome</en>
</text>
<text id="2">
<he>שם משתמש</he>
<en>username</en>
</text>
</lang>
אין הרבה מה להסביר, פתחנו בתג בשם LANG, שהוא תג הROOT, והוא מרכז בתוכו תגים בשם TEXT, עם ID, בכל תג TEXT מופיע טקסט אחר שנרצה להשתמש בו.
בכל תג TEXT יופיע התגים HE ו-EN, שאלו השפות שאני משתמש בהם (עברית ואנגלית).
תפתחו קובץ בשם lang.xml, ובתוכו תרשמו:
<?xml version="1.0" encoding="utf-8"?>
<lang>
</lang>
<lang>
</lang>
שמרו את הקובץ.
ועכשיו לקוד הPHP שיוסיף לנו תגי TEXT באופן אוטומטי:
<?php
header('Content-Type: text/html; charset=utf-8');
if(!isset($_POST['he'], $_POST['en']) || empty($_POST['he']) || empty($_POST['en'])) {
?>
<!doctype html>
<html>
<body dir="rtl">
<form method="post" action="">
<textarea id="he" name="he" placeholder="he"></textarea><br />
<textarea dir="ltr" id="en" name="en" placeholder="en"></textarea>
<input type="submit" value="שלח" />
</form>
</body>
</html>
<?php } else {
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->load('lang.xml');
$xpath = new DOMXPath($dom);
$query = $xpath->query('/lang');
$lang = $query->item(0);
$text = $dom->createElement('text');
$te = $dom->createAttribute('id');
$numID = $dom->getElementsByTagName('text')->length + 1;
$te->value = $numID;
$te = $text->appendChild($te);
$text = $lang->appendChild($text);
$he = $dom->createElement('he',$_POST['he']);
$en = $dom->createElement('en', $_POST['en']);
$text->appendChild($he);
$text->appendChild($en);
if(file_put_contents('lang.xml', $dom->saveXML())) {
?>
<html>
<body dir="rtl">
בוצע<br /><?= $numID ?><br />
<form method="post" action="">
<textarea id="he" name="he" placeholder="he"></textarea><br />
<textarea dir="ltr" id="en" name="en" placeholder="en"></textarea>
<input type="submit" value="שלח" />
</form>
</body>
</html>
<?php }} ?>
header('Content-Type: text/html; charset=utf-8');
if(!isset($_POST['he'], $_POST['en']) || empty($_POST['he']) || empty($_POST['en'])) {
?>
<!doctype html>
<html>
<body dir="rtl">
<form method="post" action="">
<textarea id="he" name="he" placeholder="he"></textarea><br />
<textarea dir="ltr" id="en" name="en" placeholder="en"></textarea>
<input type="submit" value="שלח" />
</form>
</body>
</html>
<?php } else {
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->load('lang.xml');
$xpath = new DOMXPath($dom);
$query = $xpath->query('/lang');
$lang = $query->item(0);
$text = $dom->createElement('text');
$te = $dom->createAttribute('id');
$numID = $dom->getElementsByTagName('text')->length + 1;
$te->value = $numID;
$te = $text->appendChild($te);
$text = $lang->appendChild($text);
$he = $dom->createElement('he',$_POST['he']);
$en = $dom->createElement('en', $_POST['en']);
$text->appendChild($he);
$text->appendChild($en);
if(file_put_contents('lang.xml', $dom->saveXML())) {
?>
<html>
<body dir="rtl">
בוצע<br /><?= $numID ?><br />
<form method="post" action="">
<textarea id="he" name="he" placeholder="he"></textarea><br />
<textarea dir="ltr" id="en" name="en" placeholder="en"></textarea>
<input type="submit" value="שלח" />
</form>
</body>
</html>
<?php }} ?>
מה שעשינו כאן בעצם זה בדיקה מאד פשוטה האם הגיעו אלינו נתוני POST (אם אתם מאפשרים לאנשים נוספים לתרגם, מומלץ לשפר את התנאים והאבטחה), אם לא הגיעו נתונים, יודפס לנו טופס המאפשר לנו להכניס ערכים חדשים לקובץ.
בPHP יש מחלקה מאד נוחה לעבודה עםXML בשם "simple XML", אבל אנחנו לא נעבוד איתה, אלא נעבוד עם מחלקה סבוכה יותר, כי "simple XML" מכניסה את התגים ללא הזחה, מה שמקשה על קריאת הקובץ בעת הצורך.
לאחר שבדקנו האם קיבלנו את כל הנתונים בטופס, נכניס אותם לקובץ:
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->load('lang.xml');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->load('lang.xml');
שורה ראשונה, קראנו למחלקה מובנית בPHP בשם DOMDocument, על מנת לכתוב לתוך קובץ הXML.
שורה שנייה ושלישית דואגות להזחה נכונה בתוך הקובץ.
שורה רביעית טוענת את קובץ הXML שלנו.
ועכשיו אנחנו עוברים לXPATH על מנת למצוא את תג הLANG בקובץ, ולהכניס תגי TEXT רק בתוכו.
$xpath = new DOMXPath($dom);
$query = $xpath->query('/lang');
$lang = $query->item(0);
$query = $xpath->query('/lang');
$lang = $query->item(0);
שורה ראשונה: קריאה למחלקה מובנית בPHP בשם "DOMXPath".
שורה שניה ושלישית: חיפשנו אחר תג בשם LANG, ובחרנו במופע הראשון שלו (שהוא בעצם היחיד).
בואו ניצור תג TEXT חדש, ובתוכו Attribute בשם ID, על מנת שבעתיד נוכל לגשת לשורה הנכונה באמצעות ID:
$text = $dom->createElement('text');
$te = $dom->createAttribute('id');
$te = $dom->createAttribute('id');
יצרנו תג חדש בשם TEXT, ויצרנו Attribute בשם ID, אך עדיין לא הכנסנו אותם לקובץ, ולא שייכנו אותם אחד לשני.
אז איך נדע איזה מספיר להכניס לID? פשוט מאד, נספור את תגי הTEXT הקיימים, ונוסיף להם 1.
$numID = $dom->getElementsByTagName('text')->length + 1;
$te->value = $numID;
$te->value = $numID;
בשורה הראשונה חיפשנו תג בשם TEXT, וספרנו כמה פעמים הוא מופיע במסמך, לאחר מכן הוספנו לו 1.
בשורה השנייה, לקחנו את המשתנה $te, והגדרנו לו ערך - המשתנה המחזיק את ה-ID.
עכשיו יש לשייך את הID אל תג הTEXT:
$te = $text->appendChild($te);
שימו לב למבנה המשתנים.
נגדיר שתג הTEXT יכנס לתוך תג הLANG ולא מחוצה לו:
$text = $lang->appendChild($text);
ולשלב הכי חשוב, הכנסת הערכים שקיבלנו בטופס, לתגים בשם HE, ו-EN בהתאמה:
$he = $dom->createElement('he',$_POST['he']);
$en = $dom->createElement('en', $_POST['en']);
$en = $dom->createElement('en', $_POST['en']);
ועכשיו נגדיר להם להיכנס לתוך התג TEXT ולא מחוצה לו:
$text->appendChild($he);
$text->appendChild($en);
$text->appendChild($en);
כעת נשמור את הקובץ, נדפיס חיווי למשתמש ("בוצע בהצלחה"), נדפיס את הID של התג אותו הכנסנו, ונדפיס טופס חדש, להכנסת ערכים נוספים:
if(file_put_contents('lang.xml', $dom->saveXML())) {
?>
<html>
<body dir="rtl">
בוצע<br /><?= $numID ?><br />
<form method="post" action="">
<textarea id="he" name="he" placeholder="he"></textarea><br />
<textarea dir="ltr" id="en" name="en" placeholder="en"></textarea>
<input type="submit" value="שלח" />
</form>
</body>
</html>
<?php }} ?>
?>
<html>
<body dir="rtl">
בוצע<br /><?= $numID ?><br />
<form method="post" action="">
<textarea id="he" name="he" placeholder="he"></textarea><br />
<textarea dir="ltr" id="en" name="en" placeholder="en"></textarea>
<input type="submit" value="שלח" />
</form>
</body>
</html>
<?php }} ?>
השורה הראשונה, שומרת את כל השנויים לתוך קובץ הXML, אם השינויים נשמרו בהצלחה, יודפס טופס חדש, הודעת "בוצע בהצלחה", וה-ID בו השתמשנו.
עד כאן החלק הראשון של המדריך, בקרוב אני אוסיף את החלק השני, בו אסביר איך קוראים את הקובץ, ושולפים ממנו את הערכים הנכונים.
בהצלחה :)
תגובות לכתבה:
למה לא להשתמש ב-gettext ושלום על ישראל?
למה לא להכין טמפלטים לעברית ולאנגלית ולטעון אותם לפי הצורך? הרי יש הרבה הבדלים חוץ מהשפה, לדוגמה LTR לRTL לדוגמה.
יש אפשרות להשתמש בTranslator של ZEND הוא כעיקרון מובנה בתוך ZF2 אך ניתן למצוא בגיטהאב לחוד.
@liorel100,
הקבצים נשארים אותו דבר. מה ששונה, דורסים ב-CSS וזהו.
פשוט מאד, יש לי קובץ css דינמי שמדפיס לי right או left בהתאמה לrtl או ltr.
אני לא בטוח שזה יעיל מבחינת ביצועים. צריך לחסוך כמה שיותר בחישובים על המקום.
ליאור, כל מה שיהיה צריך לעשות זה לשמור את זה בזיכרון מטמון. מכיוון שקשה לי להאמין שקבצי CSS יהיו דינמיים יותר מדי, אפשר יהיה להשתמש ב-preprocessor ובכל פעם שמשנים את הקובץ המקורי שומרים שני קבצים שונים - אחד rtl ואחד ltr.
או שאפשר ללכת על פיתרון אחר (שאני לא כל כך אוהב, אם לומר את האמת) - לעשות את זה ltr כברירת מחדל, ואם השפה היא rtl, פשוט מוסיפים קובץ CSS שידרוס את ההגדרות תלויות הכיוון. בערכות העיצוב ברירת המחדל של ו'ורדפרס עושים את זה ככה.
(הגבתי קצת באיחור, אבל עדיף מאוחר מאשר לעולם לא. לא תמיד, בעצם, אבל במקרה הזה כן. D:)
בנוגע למדריך עצמו: אני לא אוהב את השיטה הזאת. בשביל מה צריך להשתמש ב-XML ולהסתבך (סימון מיותר וקוד מפרך לפענוח) כשאפשר להשתמש ב-JSON או במערכים? ואז לתכנן מחלקה שתטפל בזה. ללארוול יש אחת כזאת. (אני לא מת עליה, וסביר להניח שאעשה לזה fork בעתיד, אבל היא טובה.) תעיף מבט: http://goo.gl/aEz76V . והנה התיעוד של זה, אם אתה רוצה: http://goo.gl/eabnP .
בכל מקרה, תודה רבה על המדריך! :-)
אני אישית אוהב לעבוד עם קבצי ini בכל מה שקשור לשפות (כמו הקונפיגרציה של PHP אותו הדבר) ויש לזה גם פונקציה מובנת ששמה את הכל במערך והחיים פשוט קלים יותר.
- למה לא להשתמש בgettext?
- @liorel100 לגבי RTL אפשר פשוט בקובץ CSS הראשי לעשות לפי LTR ובנוסף גם קובץ rtl.css שיטען לפי הצורך או פשוט לעשות קלאסים rtl/ltr שיתווספו לתג ה-html וישנו את הצדדים.
בדברים האלה חשוב לשמור על ביצועים נכונים, כי הדברים האלה יכולים להוסיף גם 0.3 שניות לטעינה ע"י הדפדפן וזה לא מצב שרוצים להגיע אליו. כשאתם עובדים עם פריימוורק MVC, הפרימוורק אמור לספק לכם מידע באופן מפורש על הlanguage של המשתמש. תצרו מחלקת קונטרולר מיוחדת אבסטרקטית שמתודת הview שלה מסננת לפי הlang שמסופק ומפנה לעמודים שונים בהתאם, בהתאם לתיקיות הטמפלטים ולפורמט הטמפלטים בהם אתם משתמשים.